#!/usr/bin/env python3
# coding: utf-8
"""
last edited: 2019-8-17
by Stone at BlueNet

采用strategy pattern，将多种识别方法分别封装为Recognizer的method，每种方法的输入为摄像头获取的
原始BGR图像，输出为目标（小球，投球点，篮筐）在照片中的(x,y)坐标以及可选的其他信息（直径）。在
更高层次的对象中，可以通过选择不同的method来灵活选用各种识别方法。图像中坐标一律以左上为原点。
照片尺寸等数据在配置文件config.py中设定。

该模块采用多进程的方案，Recognizer预计单独放在在一个进程中以提高性能。
"""
import cv2
from copy import copy, deepcopy
import logging

import config as cfg
import hmi
from robot_state import StateManager

# ===== ===== ===== ===== =====
# GLOBAL VARIABLES and INITIALIZATIONS
# ===== ===== ===== ===== =====
_RED_LOWER = copy(cfg.RED_LOWER)
_RED_UPPER = copy(cfg.RED_LOWER)

logging.basicConfig(level=logging.DEBUG)

# 用于在循环中决定执行哪项识别任务
TASK_RECOG_BALL = 0
TASK_RECOG_CAST = 1
TASK_RECOG_BASKET = 2


# ===== ===== ===== ===== =====
# FUNCTIONS
# ===== ===== ===== ===== =====
def init(state, camera_device=copy(cfg.CAMERA_DEVICE)):
    global ball_recognizer, cast_recognizer, basket_recognizer

    # StateManager.register('get_state')
    # manager = StateManager(address=deepcopy((cfg.MANAGER_IP, cfg.MANAGER_PORT)),
    #                        authkey=copy(cfg.MANAGER_AUTHKEY))
    # manager.connect()  # 如果是通过
    # state = manager.get_state()
    print('@recognizer.py get_pos', state.get_pos_ball())  # debug

    camera = Camera(camera_device=camera_device)
    ball_recognizer = BallRecognizer(state=state, camera=camera)
    cast_recognizer = CastRecognizer(state=state, camera=camera)
    basket_recognizer = BasketRecognizer(state=state, camera=camera)


def loop(state, camera_device=copy(cfg.CAMERA_DEVICE)):
    init(state=state, camera_device=camera_device)

    import time
    while True:
        time.sleep(1)
        task = state.get_recog_task()
        # task = state.get_pos_ball()
        print('current_task', task)

        if task == TASK_RECOG_BALL:
            pass
        elif task == TASK_RECOG_CAST:
            pass
        elif task == TASK_RECOG_BASKET:
            pass


# ===== ===== ===== ===== =====
# CLASSES
# ===== ===== ===== ===== =====
class Camera:
    """
    原本设计将摄像头等设备由顶层模块来统一管理的。但是考虑到图像处理需要比较高的性能，该模块比较适合
    单独放入一个进程，而多进程间传输数据的开销比较大，且摄像头与识别任务比较相关，因此摄像头就由本模
    块自行管理。

    Camera类的实例化应当在顶级模块中进行，然后传递给Recognizer类的实例。
    """
    def __init__(self, camera_device=copy(cfg.CAMERA_DEVICE)):
        self._device = camera_device
        logging.info('opening camera, device: {}'.format(self._device))
        self._cap = cv2.VideoCapture(self._device)
        if not self._cap.isOpened():
            hmi.camera_error_on()

    def read_frame(self):
        """
        正常时（ret为True），功能同cv2.VideoCapture.read()，当发生错误时（ret为False），
        将会阻塞在本函数，启动提示并且尝试重置摄像头，直到恢复正常。

        P.S. 测试时，拔出摄像头后，只有第一次读到的ret为False，此后读到的ret均为True。

        :returns: ret, frame.
        """
        ret, frame = self._cap.read()

        # read frame failed
        if not ret:
            logging.error('@BaseRecognizer.read_came|| Read frame from camera failed.')
            hmi.camera_error_on()

            # trying to reconnect to the camera when fail to read frame
            while not ret:
                self.reset_camera()
                ret, frame = self._cap.read()

            # turn off the error prompt
            hmi.camera_error_off()

        return ret, frame

    def reset_camera(self):
        return self._cap.open(self._device)


class BaseRecognizer:

    def __init__(self, camera, state):
        """
        :param camera: The Camera instance created in the top-level module.
        :param state: The state.State() object obtained by state.StateManager().get_state(),
            which has set_pos_ball() method, etc. See also robot/robot_state.py
        """
        self._cam = camera
        self._state = state


class BallRecognizer(BaseRecognizer):
    def __init__(self, state, camera):
        super().__init__(state, camera)

    def recog_hsv(self):
        img_bgr = self._cam.read_frame()
        hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

        mask = cv2.inRange(hsv, _RED_LOWER, _RED_UPPER)
        mask = cv2.erode(mask, None, iterations=2)
        mask = cv2.dilate(mask, None, iterations=2)

        _, cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        # cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
        if len(cnts) > 0:
            c = max(cnts, key=cv2.contourArea)
            # 确定面积最大的轮廓的外接圆
            ((x, y), radius) = cv2.minEnclosingCircle(c)

            logging.debug('x={}, y={}, r={}'.format(x, y, radius))

            self._state.set_pos_ball((x, y))


class CastRecognizer(BaseRecognizer):
    """投球点识别"""
    pass


class BasketRecognizer(BaseRecognizer):
    """篮筐识别"""
    pass


if __name__ == '__main__':
    # ===== ===== ===== ===== ===== ===== ===== ===== ===== ===== =====
    # 测试拔出摄像头后再插回，程序是否能自行重连。
    cam = Camera(1)
    while True:
        ret, frame = cam.read_frame()
        print('ret: ', ret)
        cv2.imshow('', frame)
        cv2.waitKey(1)
